from sqlmodel import select

from app.core.config import SETTING
from app.core.database import get_session_context
from app.models.sql.Right import Permission, RolePermission, Role
from app.services.cache_service import cache
from app.utils.class_property import ClassProperty


class RightService:
    """
    权限服务类:用于预加载权限数据、构建角色权限位图,并提供高效的权限校验能力。
    
    设计说明:
    - 权限以位图形式存储(分段 list[int]),每段长度由 SETTING.PERMISSION_BIT_LENGTH 控制(通常为 64)。
    - 用户权限位图会序列化到 JWT 中,服务端通过位运算高效校验,无需每次查数据库。
    - 所有数据在应用启动时通过 prepare_info() 预加载到内存,后续操作均为 O(1) 或 O(n)(n 为权限段数)。
    """

    # 权限 ID 到 Permission 对象的映射
    permission_dict: dict[int, Permission] = {}

    # 权限编码(如 "admin:user:read:all")到 bit_position 的映射,用于快速查找
    permission_code_map: dict[str, int] = {}

    # 角色 ID 到 Role 对象的映射
    role_dict: dict[int, Role] = {}

    # 角色权限位图:role_id -> [segment0, segment1, ...]
    # 每个 segment 是一个整数,每一位代表一个权限(1 表示拥有,0 表示无)
    role_permission_dict: dict[int, list[int]] = {}

    @classmethod
    def _set_bits_in_bitmap(cls, bit_positions: list[int], bitmap_segments: list[int]) -> list[int]:
        """
        将一组 bit_position 设置到位图段列表中(原地修改 bitmap_segments)。
        
        位图分段说明:
        - 若 PERMISSION_BIT_LENGTH = 64,则:
          - bit_position 0~63 → segment[0]
          - bit_position 64~127 → segment[1]
          - ...
        - 例如:bit_position=70 → segment_index=1, offset=6 → segment[1] |= (1 << 6)
        
        :param bit_positions: 要设置的权限位位置列表(如 [7, 70])
        :param bitmap_segments: 当前位图段列表(如 [0b101, 0b000]),函数会原地扩展并修改
        :return: 修改后的 bitmap_segments(与输入为同一对象)
        """
        for bit_pos in bit_positions:
            segment_index, offset = divmod(bit_pos, SETTING.PERMISSION_BIT_LENGTH)
            needed_segments = segment_index + 1

            # 如果当前段数不足,扩展至 needed_segments
            if len(bitmap_segments) < needed_segments:
                bitmap_segments.extend([0] * (needed_segments - len(bitmap_segments)))

            # 在对应段中设置该位
            bitmap_segments[segment_index] |= (1 << offset)

        return bitmap_segments

    @classmethod
    async def prepare_info(cls):
        """
        应用启动时调用:预加载所有权限、角色及角色-权限关联数据到内存。
        
        执行步骤:
        1. 加载所有 Permission、Role、RolePermission 表数据。
        2. 构建 permission_dict 和 permission_code_map。
        3. 为每个角色构建分段权限位图(role_permission_dict)。
        
        注意:
        - 无效的 RolePermission(如 permission_id 不存在)会被跳过。
        - 位图按 bit_position 自动分段,支持任意数量的权限。
        """
        async with get_session_context() as session:
            # 并行查询三张表
            select_permissions = select(Permission).order_by(Permission.bit_position)
            select_roles = select(Role).order_by(Role.id)
            select_role_permissions = select(RolePermission)

            perm_result = await session.exec(select_permissions)
            role_result = await session.exec(select_roles)
            rp_result = await session.exec(select_role_permissions)

            # 构建权限字典
            permissions = perm_result.all()
            cls.permission_dict = {p.id: p for p in permissions}
            cls.permission_code_map = {p.code: p.bit_position for p in permissions}

            # 构建角色字典
            cls.role_dict = {r.id: r for r in role_result.all()}

            # 构建角色权限位图
            cls.role_permission_dict.clear()
            for rp in rp_result.all():
                perm = cls.permission_dict.get(rp.permission_id)
                if perm is None:
                    continue  # 跳过无效权限关联(可记录日志)

                role_id = rp.role_id
                bit_pos = perm.bit_position

                # 获取或初始化该角色的位图段列表
                if role_id not in cls.role_permission_dict:
                    cls.role_permission_dict[role_id] = []
                segments = cls.role_permission_dict[role_id]

                # 将当前权限位设置到位图中
                cls._set_bits_in_bitmap([bit_pos], segments)

    @classmethod
    def get_merged_permission_bitmap(cls, role_ids: list[int]) -> list[int]:
        """
        根据用户的角色 ID 列表,合并生成用户的完整权限位图。
        
        合并规则:按位 OR(只要任一角色拥有该权限,用户就拥有)。
        
        :param role_ids: 用户拥有的角色 ID 列表
        :return: 合并后的权限位图,格式为 [segment0, segment1, ...]
        """
        if not role_ids:
            return []

        merged_bitmap = []
        for role_id in role_ids:
            role_bitmap = cls.role_permission_dict.get(role_id, [])
            # 确保 merged_bitmap 长度足够
            if len(role_bitmap) > len(merged_bitmap):
                merged_bitmap.extend([0] * (len(role_bitmap) - len(merged_bitmap)))
            # 按位 OR 合并
            for i, segment in enumerate(role_bitmap):
                merged_bitmap[i] |= segment

        return merged_bitmap

    @classmethod
    def check_permission(cls, required_permission_codes: list[str], user_permission_bitmap: list[int]) -> bool:
        """
        校验用户是否拥有所有指定的权限。
        
        流程:
        1. 将 required_permission_codes 转换为 bit_position 列表。
        2. 构建所需的权限位图(require_bitmap)。
        3. 与用户权限位图(user_permission_bitmap)按段比较:
           - (user_segment & require_segment) == require_segment 表示权限满足。
        
        安全说明:
        - user_permission_bitmap 应来自可信 JWT payload(已签名,不可伪造)。
        - 若 required_permission_codes 中包含未知权限,直接返回 False。
        
        :param required_permission_codes: 所需权限编码列表,如 ["admin:user:read:all"]
        :param user_permission_bitmap: 用户权限位图(来自 JWT),如 [0b10101, 0b11101]
        :return: True 表示用户拥有全部所需权限,否则 False
        """
        # 1. 转换权限编码为 bit_position
        required_bits = []
        for code in required_permission_codes:
            bit_pos = cls.permission_code_map.get(code)
            if bit_pos is None:
                return False  # 未知权限,拒绝访问
            required_bits.append(bit_pos)

        # 2. 构建所需的权限位图
        require_bitmap = cls._set_bits_in_bitmap(required_bits, [])

        # 3. 安全校验:不修改输入的 user_permission_bitmap
        # 对齐长度:若 user_bitmap 段数不足,视为高位为 0
        user_bitmap_padded = user_permission_bitmap + [0] * max(0, len(require_bitmap) - len(user_permission_bitmap))

        # 4. 逐段检查是否满足所有权限
        for req_seg, user_seg in zip(require_bitmap, user_bitmap_padded):
            if (user_seg & req_seg) != req_seg:
                return False
        return True

    @ClassProperty
    @cache(expire=SETTING.PERMISSION_CACHE_EXPIRE)
    def permission(cls):
        """
        权限列表缓存
        """
        return list(cls.permission_dict.values())

    @ClassProperty
    @cache(expire=SETTING.PERMISSION_CACHE_EXPIRE)
    def role(cls):
        """
        角色列表缓存
        """
        return list(cls.role_dict.values())

    @ClassProperty
    @cache(expire=SETTING.PERMISSION_CACHE_EXPIRE)
    def role_permission(cls):
        """
        角色权限列表缓存
        """
        return list(cls.role_permission_dict.values())
